#include "FileSystem.h"

FileSystem::FileSystem(Disk* disk): disk(disk) {
  blockSize = disk->getBlockSize();
}

FileSystem::~FileSystem()
{
}

#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) > (b) ? (b) : (a))

// creates a file returning its i-node number.
//Flag indicates whether the file to create is a regular file or a soft link.
int FileSystem::createFile(int flag){
  int inode=disk->allocateInode();
  if(inode==-1) {
    cerr<< "can't create file - no free inodes." <<endl;
    return -1;
  }
  disk->setInodeType(inode, flag);
  disk->setFileSize(inode, 0);
  disk->dumpToFile();
  return inode;
}


 //– Returns the type of the file this i-node represents.
int FileSystem::getFileType(int inode){
  return disk->getInodeType(inode);
}

//– Returns the type of the file this i-node represents.
int FileSystem::getFileSize(int inode){
 return disk->getFileSize(inode);
}

//– reads nBytes (at most) from the given file (represented by this i-node)
//at the given offset inside the file into the buffer.
int FileSystem::f_read( int inode, byte* buffer, int offset, int nBytes ) {
	//cout << "leSystem::f_read in "<<endl;
  int count=0;
  int fileSize=disk->getFileSize(inode);
  if(offset>=fileSize) {
    //cout<< "FileSystem::f_read: invalid offset "<<offset << " for file with size "<<fileSize <<endl;

	  //no bytes to read:
    return 0;
  }
  nBytes = min(nBytes, fileSize - offset);
  for(int i=offset/blockSize; count<nBytes; i++) {
    int n = min(blockSize - (offset % blockSize), nBytes-count);
    int b = disk->getDataBlock(inode, i);
    if(b==-1) {
    	cout << "FileSystem::f_read  PROBLEM: the "<<i<<"'th datablock is -1 in inode "<<inode<<endl;
    }
    else {
    	disk->readBlock(b, buffer + count, (offset % blockSize), n);
    }

    offset+=n;
    count+=n;
    //pthread_yield();
  }
  //cout << "FileSystem::f_read out "<<endl;
  return count;
}

//int Notice that both f_read and f_write only work on normal files (not directories).
//– writes nBytes (at most) to the given file (represented by this i-node) at the given
//offset inside that file, from the buffer. If the amount to be written exceeds the current
//allocated block, a new block should be allocated to accommodate the rest of the data.
int FileSystem::f_write( int inode, const byte* buffer, int offset, int nBytes ){
  int count=0;
  int filesize=disk->getFileSize(inode);

  if(offset > filesize) {
	  //fill in the blanks
	  int diff=offset-filesize;
	  byte fill[diff];
	  if(f_write(inode, fill, filesize, diff) < diff) {
		  cout << "FileSystem::f_write: couldn't fill out sparse file."<<endl;
		  return -1;
	  }
  }

  for(int i=offset/blockSize; count<nBytes; i++) {
    int n = min(blockSize - (offset % blockSize), nBytes - count);
    //cout << "FileSystem::f_write getting block "<<i<<endl;
    int b = disk->getDataBlock(inode, i);
    if(b==-1) {
    	//cout << "FileSystem::f_write b=-1"<<endl;
      b=disk->allocateDataBlock();
      //cout << "FileSystem::f_write b="<<b<<endl;
      if(b==-1){
    	  cout << "FileSystem::f_write FAILED: couldn't allocate another free block!"<<endl;
    	  //disk->loadFromFile(); //undo
    	  disk->setFileSize(inode, offset);
  		  return -1;
      }
      //cout << "allocated block "<<b<<endl;

      if(disk->setDataBlock(inode, i, b)==-1) {
      	 cout << "FileSystem::f_write FAILED: couldn't add another block to the inode!"<<endl;
      	 disk->freeBlock(b);
      	 //disk->loadFromFile(); //undo
      	 disk->setFileSize(inode, offset);
      	 return -1;
      }

      //todo: test sync
      cout << pthread_self() << " [ ";
      pthread_yield();
      cout << "- ] "<< pthread_self() <<endl;
    }
    //cout << "the "<<i<<"'th block is "<< b<<endl;
    disk->writeBlock(b, buffer + count, (offset % blockSize), n);
    offset+=n;
    count+=n;
  }
  if(offset > filesize)
	  disk->setFileSize(inode, offset);
  disk->dumpToFile();
  return count;
}

//– delete the file.
int FileSystem::f_delete(int inode) {
	//cout <<"FileSystem::f_delete inode="<<inode<<endl;
	if(disk->decNumHardLinks(inode)>0) {
		cout <<  "FileSystem::f_delete didn't actually delete inode "<<inode<<" because there are hard-links to it."<<endl;
		return 0;
	}

	if(disk->getFileSize(inode)>0) {
		for(int i=(disk->getFileSize(inode)-1)/blockSize; i>=0; i--) {
			int b=disk->getDataBlock(inode, i);
			if(b!=-1) {
				disk->freeBlock(b);
			}
			else {
				cout<< "FileSystem::f_delete error: inode "<<inode<<"'s data block number "<<i<<" was -1."<<endl;
			}
		}
	}

	int ans = disk->freeInode(inode);
	disk->dumpToFile();
	return ans;
}







//– creates a directory, returning its i-node number.
int FileSystem::createDir(){
  int inode=createFile(DIRECTORY);
  Directory temp(0);
  d_write(inode, &temp);
  return inode;
}

//– reads the entire content of the directory file specified by the given i-node.
//This information is then entered into a data structure (a vector or list) that lists
//the files in this directory. Each element in this list therefore contains a filename
//and i-node number.
Directory* FileSystem::d_read(int inode) {
	//cout << "FileSystem::d_read in , disk->getFileSize(inode) ="<<disk->getFileSize(inode) <<endl;
	int dir_size = disk->getFileSize(inode);
	Directory* dir=new Directory(dir_size / sizeof(DirectoryEntry));
	if(dir->size>0 && f_read(inode, (byte*)dir->contents, 0, dir_size) < dir_size) {
		cout << "FileSystem::d_read: f_read failed..." <<endl;
		delete dir;
		return NULL;
	}
	//cout << "FileSystem::d_read in "<<endl;
	return dir;
}

// – replaces the content of the given directory file (specified by i-node)
//with the information stored in the list.
//The list has the same format as that returned by d_read().
int FileSystem::d_write(int inode, Directory* dir ) {
	if(disk->getInodeType(inode)!=DIRECTORY) {
		cout << "FileSystem::d_write "<<inode <<" is not a directoty!" <<endl;
		return -1;
	}

	int oldsize = disk->getFileSize(inode);
	int size=dir->size * sizeof(DirectoryEntry);

	//if directory file was reduced - free the redundant blocks, if any
	//if(size < oldsize) {
	for(int i=(size+blockSize-1) / blockSize; i< (oldsize+blockSize-1)/ blockSize; i++) {
		int b=disk->getDataBlock(inode, i);
		if(b!=-1) {
			disk->freeBlock(b);
		}
		else {
			cout << "FileSystem::d_write problem in truncating directory!"<<endl;
		}
		disk->setDataBlock(inode, i, -1);
	}
	//}

	//this is needed because f_write can only increase filse size!
	disk->setFileSize(inode, size);

	return f_write(inode, (byte*)dir->contents, 0, size);
}


//– delete the directory if it is empty.
int FileSystem::d_delete(int inode){
	if(disk->getInodeType(inode)!=DIRECTORY) {
		cout << "FileSystem::d_delete " << inode <<" is not a directoty!" <<endl;
		return -1;
	}
	if(disk->getFileSize(inode)>0) {
		cout << "FileSystem::d_delete " << "can't delete non-empty directory!" <<endl;
		return -1;
	}
	return f_delete(inode);
}

int FileSystem::incNumHardLinks(int inode) {
	int ans = disk->incNumHardLinks(inode);
	disk->dumpToFile();
	return ans;
}

int FileSystem::getNumHardLinks(int inode) {
	return disk->getNumHardLinks(inode);
}

void FileSystem::status() {
	cout << "FileSystem status:  nominal :)"<<endl;
}

